home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1999 September (IDG) / Sep99.iso / Shareware World / Utilities / Text Processing / Alpha / Tcl / SystemCode / indentation.tcl < prev    next >
Encoding:
Text File  |  1999-04-21  |  17.1 KB  |  594 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #    Vince's    Additions -    an extension package for Alpha
  4.  # 
  5.  #    FILE: "indentation.tcl"
  6.  #                      created: 27/7/97 {1:08:08 am}    
  7.  #                     last update: 21/4/1999 {2:48:18 pm}    
  8.  #    Author:    Vince Darley
  9.  #    E-mail:    <darley@fas.harvard.edu>
  10.  #      mail:    Division of Applied Sciences, Harvard University
  11.  #            Oxford Street, Cambridge MA 02138, USA
  12.  #       www:    <http://www.fas.harvard.edu/~darley/>
  13.  #    
  14.  # ###################################################################
  15.  ##
  16.  
  17. alpha::flag electricBraces 0.1 {global C C++ Java Tcl Perl} help {
  18.     Enabling the 'Electric Braces' feature tells Alpha to treat the 
  19.     left or right brace '{', '}' keys as special keypresses which 
  20.     enter the '{' or '}' character, followed by a return and then 
  21.     indent the following line correctly.  It is useful for those 
  22.     programming modes in which '{' and '}' are used to delineate 
  23.     blocks of code in 'for' loops or 'if-then-else' groups etc.
  24. }
  25.  
  26. alpha::flag electricSemicolon 0.1 {global C C++ Java Perl} help {
  27.     Enabling the 'Electric Semicolon' feature tells Alpha to treat the 
  28.     semicolon key ';' as special keypresses which enters the ';' 
  29.     character followed by a return and then indents the following line 
  30.     correctly.  It is useful for some programming modes in which ';' 
  31.     normally ends a line.
  32.     
  33.     The ';' key is context-dependent so you can still enter a 
  34.     for( ; ; ) loop in C mode (for instace) without Alpha messing 
  35.     things up.
  36. }
  37.  
  38. alpha::feature electricReturn 0.1 {global} {
  39.     if {[info tclversion] >= 8.0} {
  40.     linkVar indentOnReturn
  41.     }
  42.     set indentOnReturn 0
  43. } {set indentOnReturn 1} {set indentOnReturn 0} help {
  44.     Enabling the 'Electric Return' feature tells Alpha to indent the 
  45.     following line automatically whenever you press return.
  46. }
  47.  
  48. alpha::flag electricColon 0.1 {global} help {
  49.     Enabling the 'Electric Colon' feature tells Alpha to carry out a 
  50.     special action when the user presses colon.
  51. }
  52.  
  53. alpha::flag autoContinueComment 0.1 {global} help {
  54.     Enabling the 'autoContinueComment' feature tells Alpha to check when
  55.     the users hits return whether the current line is a comment, and if
  56.     so, to indent and insert comment characters so that the following
  57.     line continues the comment.
  58. }
  59.  
  60. alpha::flag indentUsingSpacesOnly 0.1 {global TeX} help {
  61.     If set, do not use tabs to indent, but spaces only.  This is mostly
  62.     useful for modes in which the 'tab' character has a special meaning,
  63.     such as python or TeX (the latter usually only for TeX as a programming
  64.     language, not as a document preparation system).
  65. }
  66.  
  67. alpha::flag commentsArentSpecialWhenIndenting 0.1 {global TeX} help {
  68.     Indent lines to level of previous line if set, otherwise to level 
  69.     of previous non-comment line (in which case Alpha will search 
  70.     backwards for some distance).  If you're in the habit of indenting 
  71.     your comments to the same level as your code, this setting 
  72.     shouldn't matter (and setting it is slightly more efficient).
  73.     
  74.     One case in which it can be _much_ more efficient is when your 
  75.     files contain vast comments (especially .dtx files in TeX mode, 
  76.     for instance).  For these files, you should activate this feature.
  77. }
  78.  
  79. namespace eval indent {}
  80. namespace eval Bind {}
  81. namespace eval text {}
  82.  
  83. proc IndentLine {} { bind::IndentLine }
  84.  
  85. proc typeText {t} {
  86.     if {[isSelection]} {
  87.     deleteSelection
  88.     }
  89.     insertText $t
  90. }
  91.  
  92. proc normalLeftBrace {} {
  93.     typeText "\{"
  94. }
  95. proc normalRightBrace {} {
  96.     typeText "\}"
  97.     blink [matchIt "\}" [pos::math [getPos] - 2]]
  98. }
  99.             
  100. proc literalChar {} {
  101.     return [expr {[lookAt [pos::math [getPos] - 1]] == "\\"}]
  102. }
  103.  
  104. # ◊◊◊◊ Electric indentation ◊◊◊◊ #
  105. proc bind::LeftBrace {} {
  106.     if {[isSelection]} { deleteSelection }
  107.     global electricBraces mode
  108.     if {!$electricBraces} {
  109.     insertText "\{"
  110.     return
  111.     }
  112.     mode::proc electricLeft
  113. }
  114.  
  115. proc ::electricLeft {} {
  116.     if {![catch {search -l [lineStart [pos::math [lineStart [getPos]] - 1]] \
  117.       -s -f 0 -r 0 "\}" [getPos]} res]} {
  118.     set end [getPos]
  119.     if {[pos::compare [getPos] != [maxPos]]} {
  120.         set end [pos::math $end + 1]
  121.     }
  122.     
  123.     if {[regexp -- "\}\[ \t\r\n\]*else" [getText [lindex $res 0] $end]]} {
  124.         set res2 [search -s -f 0 -r 1 {else} [getPos]]
  125.         oneSpace
  126.         set text [getText [lindex $res2 0] [getPos]]
  127.         if {[lookAt [pos::math [getPos] - 1]] != " "} {
  128.         append text " "
  129.         }
  130.         replaceText [pos::math [lindex $res 0] + 1] [getPos] " $text\{\r"
  131.         bind::IndentLine
  132.         return 
  133.     }
  134.     }
  135.     set pos [getPos]
  136.     set i [text::firstNonWsLinePos $pos]
  137.     
  138.     if {([pos::compare $i == $pos]) || ([lookAt [pos::math $pos - 1]] == " ")} {
  139.     insertText "\{\r" [text::indentString $pos] [text::Tab]
  140.     } else {
  141.     insertText " \{\r" [text::indentString $pos] [text::Tab]
  142.     }
  143. }
  144.  
  145. proc ::electricRight {} {
  146.     set pos [getPos]
  147.     set start [lineStart $pos]
  148.     
  149.     if {[catch {matchIt "\}" [pos::math $pos - 1]} matched]} {
  150.     beep
  151.     message "No matching '\{'!"
  152.     return
  153.     }
  154.     set text [getText [lineStart $matched] $matched]
  155.     regexp "^\[ \t\]*" $text indentation
  156.     if {[string trim [getText $start $pos]] != ""} {
  157.     insertText "\r" $indentation "\}\r" $indentation
  158.     blink $matched
  159.     return
  160.     }
  161.     set text "${indentation}\}\r$indentation"
  162.     replaceText $start $pos $text
  163.     goto [pos::math $start + [string length $text]]
  164.     blink [matchIt "\}" [pos::math $start - 2]]
  165. }
  166.  
  167. proc bind::RightBrace {} {
  168.     if {[isSelection]} { deleteSelection }
  169.     global electricBraces mode
  170.     if {!$electricBraces} {
  171.     insertText "\}"
  172.     catch {blink [matchIt "\}" [pos::math [getPos] - 2]]}
  173.     return
  174.     }
  175.     mode::proc electricRight
  176. }
  177.  
  178. proc bind::electricSemi {} {
  179.     if {[isSelection]} { deleteSelection }
  180.     global electricSemicolon mode
  181.     if {!$electricSemicolon} {
  182.     insertText ";"
  183.     return
  184.     }
  185.     mode::proc electricSemi
  186. }
  187.  
  188. proc ::electricSemi {} {
  189.     set pos [getPos]
  190.     set start [lineStart $pos]
  191.     set text [getText $start $pos]
  192.     
  193.     if {[string first "for" $text] != "-1"} {
  194.     set paren 0
  195.     set len [string length $text]
  196.     for {set i 0} {$i < $len} {incr i} {
  197.         switch -- [string index $text $i] {
  198.         "("    { incr paren }
  199.         ")"    { incr paren -1 }
  200.         }
  201.     }
  202.     if {$paren != 0} {
  203.         insertText ";"
  204.         return
  205.     }
  206.     }
  207.     
  208.     insertText ";\r" [text::indentString $pos]
  209. }
  210.  
  211. ## 
  212.  # -------------------------------------------------------------------------
  213.  #     
  214.  # "bind::CarriageReturn" --
  215.  #    
  216.  #    General    purpose    CR procedure.  Should be bound to 'return' for all 
  217.  #    modes really.  Calls a mode-specific procedure if required.
  218.  # -------------------------------------------------------------------------
  219.  ##
  220. proc bind::CarriageReturn {} {
  221.     if {[isSelection]} { deleteSelection }
  222.     global autoContinueComment
  223.     if {$autoContinueComment && ([text::isInComment [set p [getPos]] start])} {
  224.     # special case for beginning of line
  225.     if {[pos::compare $p == [lineStart $p]]} {
  226.         backwardChar
  227.     }
  228.     insertText "\r${start}"
  229.     return
  230.     }
  231.     mode::proc carriageReturn
  232. }
  233.  
  234. proc ::carriageReturn {} {
  235.     insertText "\r"
  236.     global indentOnReturn
  237.     if {$indentOnReturn} {bind::IndentLine}
  238. }
  239.  
  240. proc bind::IndentLine {} {
  241.     mode::proc indentLine
  242. }
  243.  
  244. proc insertActualTab {} { typeText "\t" }
  245.  
  246.  
  247.  
  248. ## 
  249.  # -------------------------------------------------------------------------
  250.  # 
  251.  # "text::isInComment" --
  252.  # 
  253.  # Are we in a block comment? Just checks if both the given line and the
  254.  # next line commence with any of a set of known block-comment characters.
  255.  # Not 100% satisfactory for C comments, but fine for all others.
  256.  # -------------------------------------------------------------------------
  257.  ##
  258. proc text::isInComment {pos {st ""}} {
  259.     if {[pos::compare $pos == [minPos]]} {
  260.     return 0
  261.     }
  262.     set p [lineStart $pos]
  263.     if {[pos::compare $pos == $p]} {
  264.     set pos [pos::math $pos - 1] ; set p [lineStart $pos]
  265.     }
  266.     set q [nextLineStart $pos]
  267.     set t [getText $p $q]
  268.     if { $st != "" } {
  269.     upvar $st a
  270.     }
  271.     if {![catch {commentCharacters "Paragraph"} cpar]} {
  272.     if {[regexp -- "^\[ \t\]*[quote::Regfind [lindex $cpar 0]]" $t a]} {
  273.         if {![regexp -- "[quote::Regfind [lindex $cpar 1]]" $t]} {
  274.         set len [string length [lindex $cpar 2]]
  275.         set a [string range $a 0 [expr {[string length $a] - $len -1}]]
  276.         append a [lindex $cpar 2]
  277.         return 1
  278.         }
  279.     }
  280.     }
  281.     # if the next line is a comment 
  282.     set qq [text::firstNonWsLinePos $q]
  283.     if {[pos::compare $qq == [maxPos]]} { 
  284.     return 0 
  285.     }
  286.     foreach commentCh [commentCharacters "General"] {    
  287.     if {[regexp -- "^\[ \t\]*[quote::Regfind ${commentCh}]\[ \t\]*" $t a]} {
  288.         # if we hit return in the middle of a line
  289.         if {[string trim [getText $pos $q]] != "" && [pos::compare $pos != $p]} { 
  290.         return 1
  291.         }
  292.         if {[getText $qq [pos::math $qq + [string length $commentCh]]] == $commentCh} {
  293.         return 1
  294.         }
  295.     }
  296.     }
  297.     return 0
  298. }
  299.  
  300.  
  301. # ◊◊◊◊ Indentation utility routines ◊◊◊◊ #
  302.  
  303. proc posX {pos} {return [lindex [posToRowCol $pos] 1] }
  304. # the above version doesn't work!
  305. if {[info tclversion] < 8.0} {
  306. proc posX {pos} {return [string length [text::maxSpaceForm [getText [lineStart $pos] $pos]]]}
  307. }
  308.  
  309. proc text::firstNonWs {pos} {
  310.     set p [text::firstNonWsPos $pos]
  311.     if {[pos::compare $p > [minPos]]} {
  312.     return [lookAt $p]
  313.     } else {
  314.     return ""
  315.     }
  316. }
  317.  
  318. ## 
  319.  # -------------------------------------------------------------------------
  320.  #   
  321.  # "text::firstNonWsPos" --
  322.  #  
  323.  #  This returns the position of the first non-whitespace character from
  324.  #  the start of pos' line.  It need not return something on the same
  325.  #  line.
  326.  # -------------------------------------------------------------------------
  327.  ##
  328. proc text::firstNonWsPos {pos} {
  329.     if {[catch {lindex [search -s -f 1 -r 1 "\[^ \t\r\n\]" [lineStart $pos]] 0} res]} {
  330.     return [lineStart $pos]
  331.     } else {
  332.     return $res
  333.     }
  334. }
  335.  
  336. proc text::firstNonWsLinePos {pos} {
  337.     if {[catch {lindex [search -s -f 1 -r 1 "\[^ \t\]" [lineStart $pos]] 0} res]} {
  338.     return [lineStart $pos]
  339.     } else {
  340.     return $res
  341.     }
  342. }
  343.  
  344. proc text::indentation {pos} {
  345.     return [search -s -m 0 -f 1 -r 1 "^\[ \t\]*\[^ \t\]" [lineStart $pos]]
  346. }
  347.  
  348. ## 
  349.  # -------------------------------------------------------------------------
  350.  # 
  351.  # "text::minSpaceForm" --
  352.  # 
  353.  #  Converts to minimal form: tabs then spaces.  Uses one regsub to do
  354.  #  the job.  Note that the regexp used relies upon the left-to-right
  355.  #  priority of branch matching.  If the regexp library used is more
  356.  #  sophisticated and finds maximal matches, then this is no good.
  357.  #  In that case use:
  358.  #        regsub -all $sp $ws "\t" ws
  359.  #        regsub -all " +\t" $ws "\t" ws
  360.  # -------------------------------------------------------------------------
  361.  ##
  362. if {[info tclversion] < 8.1} {
  363.     proc text::minSpaceForm {ws} {
  364.     regsub -all "([spacesEqualTab]| +\t)" $ws "\t" ws
  365.     return $ws
  366.     }
  367. } else {
  368.     proc text::minSpaceForm {ws} {
  369.     regsub -all [spacesEqualTab] $ws "\t" ws
  370.     regsub -all " +\t" $ws "\t" ws
  371.     return $ws
  372.     }
  373. }
  374.  
  375.  
  376. ## 
  377.  # -------------------------------------------------------------------------
  378.  # 
  379.  # "text::maxSpaceForm" --
  380.  # 
  381.  #  Converts it to maximal form - just spaces.
  382.  #  Just uses one funky regsub to do the job!  Takes account of tab-size,
  383.  #  spaces interspersed with tabs,...
  384.  # -------------------------------------------------------------------------
  385.  ##
  386. if {[info tclversion] < 8.1} {
  387.     proc text::maxSpaceForm {ws} {
  388.     set sp [spacesEqualTab]
  389.     regsub -all "(($sp)*) *\t" $ws "\\1$sp" ws
  390.     return $ws
  391.     }
  392. } else {
  393.     proc text::maxSpaceForm {ws} {
  394.     set sp [spacesEqualTab]
  395.     regsub -all $sp $ws "\t" ws
  396.     regsub -all " +\t" $ws "\t" ws
  397.     regsub -all "\t" $ws "$sp" ws
  398.     return $ws
  399.     }
  400. }
  401.  
  402.  
  403. ## 
  404.  # -------------------------------------------------------------------------
  405.  # 
  406.  # "spacesEqualTab" --
  407.  # 
  408.  #  Return the number of spaces equivalent to a single tab. If tabs are too
  409.  #  big, this won't work.
  410.  # -------------------------------------------------------------------------
  411.  ##
  412. proc spacesEqualTab {} {
  413.     getWinInfo a
  414.     string range "              " 1 $a(tabsize)
  415. }
  416.  
  417. proc doubleLookAt {pos} {return [getText $pos [pos::math $pos + 2]]}
  418.  
  419. set bind::_IndentSpaces "                                                   \
  420.                                          "
  421. set bind::_IndentTabs "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
  422.  
  423. proc text::indentOf {size} {
  424.     global bind::_IndentSpaces bind::_IndentTabs indentUsingSpacesOnly
  425.     if {$indentUsingSpacesOnly} {
  426.     return [string range ${bind::_IndentSpaces} 1 $size]
  427.     } else {
  428.     getWinInfo a
  429.     set ret [string range ${bind::_IndentTabs} 1 [expr $size / $a(tabsize)]]
  430.     append ret [string range ${bind::_IndentSpaces} 1 [expr $size % $a(tabsize)]]
  431.     }
  432.     return $ret
  433. }
  434.  
  435. # returns the indent string of the line named by 'pos'
  436. proc text::indentString {pos} {
  437.     set beg [lineStart $pos]
  438.     regexp "^\[ \t\]*" [getText $beg [nextLineStart $beg]] white
  439.     return $white
  440. }
  441.  
  442. # returns the indent string of the line up to position 'pos' 
  443. proc text::indentTo {pos} {
  444.     regexp "^\[ \t\]*" [getText [lineStart $pos] $pos] white
  445.     return $white
  446. }
  447.  
  448. ## 
  449.  # -------------------------------------------------------------------------
  450.  # 
  451.  # "text::indentBy" --
  452.  # 
  453.  #  Take the given block of text, and insert/remove spaces and tabs to
  454.  #  indent it $by spaces to the left or right. This version should work
  455.  #  ok for Tcl 7.5/8.0/8.1
  456.  # -------------------------------------------------------------------------
  457.  ##
  458. proc text::indentBy {text by} {
  459.     global bind::_IndentSpaces indentUsingSpacesOnly
  460.     set sp [spacesEqualTab]
  461.     # Convert all leading whitespace to spaces
  462.     while {[regsub -all "((^|\r|\n)($sp)*) *\t" $text "\\1$sp" text]} {}
  463.     set sby [string range ${bind::_IndentSpaces} 1 [expr {abs($by)}]]
  464.     if {$by < 0} {
  465.     # need to indent less
  466.     regsub -all "(^|\r|\n)$sby" $text "\\1" text
  467.     } else {
  468.     # need to indent more: add spaces to beginning of each line,
  469.     # apart from blank lines and the final line
  470.     regsub -all "\[\r\n\](\[^\r\n\])" $sby$text "\r$sby\\1" text
  471.     }
  472.     # We already converted everything to spaces, so we only convert
  473.     # to tabs if the user wants them.
  474.     if {!$indentUsingSpacesOnly} {
  475.     while {[regsub -all "((^|\r|\n)\t*)$sp" $text "\\1\t" text]} {}
  476.     }
  477.     return $text
  478. }
  479.  
  480. proc text::halfTab {} {
  481.     global indent_amounts
  482.     return [string range "              " 1 $indent_amounts(1)]
  483. }
  484. proc text::Tab {} {
  485.     global indentationAmount
  486.     return [text::indentOf $indentationAmount]
  487. }
  488.  
  489. proc text::getTabSize {} {
  490.     getWinInfo a
  491.     return $a(tabsize)
  492. }
  493.  
  494. # ◊◊◊◊ General purpose indentation ◊◊◊◊ #
  495.  
  496. proc indentSelection {} {
  497.     mode::proc indentRegion
  498. }
  499.  
  500. ## 
  501.  # -------------------------------------------------------------------------
  502.  # 
  503.  # "text::inCommentBlock" --
  504.  # 
  505.  #  Returns 'startpos endpos' if true, else returns an error.  Not 
  506.  #  particularly robust, but not too bad either
  507.  # -------------------------------------------------------------------------
  508.  ##
  509. proc text::inCommentBlock {pos} {
  510.     set chars [commentCharacters Paragraph]
  511.     set start [string trim [lindex $chars 0]]
  512.     set end [string trim [lindex $chars 1]]
  513.     if {$start == $end} {
  514.     error "No"
  515.     }
  516.     set cS [search -s -f 0 -r 0 -l [pos::math $pos - 1000] $start $pos]
  517.     set cE [search -s -f 1 -r 0 -l [pos::math $pos + 1000] $end [lindex $cS 1]]
  518.     if {[pos::compare $pos >= [lindex $cE 1]]} {    
  519.     error "No"
  520.     } else {
  521.     return [list [lindex $cS 0] [lindex $cE 1]]
  522.     }
  523. }
  524.  
  525.  
  526. # Tom's new regexp which I don't use now.  Shame.
  527. #set commentRegexp       {/\*[^*]*\*+([^/*][^*]*\*+)*/}
  528.  
  529. #########################################################################
  530. # Generic C-style indentation (works for Tcl and Perl)
  531. # Significant changes by Vince.
  532. proc ::indentLine {} {
  533.     global commentsArentSpecialWhenIndenting
  534.     # get details of current line
  535.     set beg [lineStart [getPos]]
  536.     set text [getText $beg [nextLineStart $beg]]
  537.     regexp "^\[ \t\]*" $text white
  538.     set len [string length $white]
  539.     set epos [pos::math $beg + $len]
  540.  
  541.     if {[pos::compare $beg != [minPos]]} {
  542.     # Find last previous non-comment line and get its leading whitespace
  543.     set pos $beg
  544.     while 1 {
  545.         if {[pos::compare $pos == [minPos]] || [catch {search -s -f 0 -r 1 -i 0 -m 0 "^\[ \t\]*\[^ \t\r\n\]" [pos::math $pos - 1]} lst]} {
  546.         # search failed at top of file
  547.         set line "#"
  548.         set lwhite 0
  549.         break
  550.         }
  551.         if {!$commentsArentSpecialWhenIndenting && \
  552.           ![catch {text::inCommentBlock [lindex $lst 0]} res]} {
  553.         set pos [lindex $res 0]
  554.         } else {
  555.         set line [getText [lindex $lst 0] [pos::math [nextLineStart [lindex $lst 0]] - 1]]
  556.         set lwhite [posX [pos::math [lindex $lst 1] - 1]]    
  557.         break
  558.         }
  559.     }
  560.     
  561.     regexp "(\[^ \t\])\[ \t\]*\$" $line "" nextC
  562.     global indentationAmount electricColon
  563.     if {($nextC == "\{")} {
  564.         incr lwhite $indentationAmount
  565.     } elseif {$nextC == ":" && $electricColon} {
  566.         incr lwhite [expr {$indentationAmount /2}]
  567.     }
  568.     
  569.     if {[regexp ":\[ \t\r\n\]*\$" $text] && $electricColon} {incr lwhite [expr {-$indentationAmount / 2}]}
  570.     if {[lookAt $epos] == "\}"} {
  571.         incr lwhite [expr {-$indentationAmount}]
  572.     }
  573.     } else {
  574.     set lwhite 0
  575.     }
  576.     set lwhite [text::indentOf $lwhite]
  577.     if {$white != $lwhite} {
  578.     replaceText $beg $epos $lwhite
  579.     }
  580.     goto [pos::math $beg + [string length $lwhite]]
  581. }
  582.  
  583.  
  584. proc ::indentRegion {} {
  585.     set from [lindex [posToRowCol [getPos]] 0]
  586.     set to [lindex [posToRowCol [selEnd]] 0]
  587.     select [getPos]
  588.     while {$from <= $to} {
  589.     goto [rowColToPos $from 0]
  590.     bind::IndentLine
  591.     incr from
  592.     }
  593. }
  594.